package com.scheduler.backend.aps.deadlinefirst;

import com.scheduler.backend.aps.domain.*;
import com.scheduler.backend.aps.domain.deadlinefirst.OrderSlice;
import com.scheduler.backend.aps.domain.deadlinefirst.ScheduleTable;
import com.scheduler.backend.data.repository.*;
import com.scheduler.backend.enums.ShiftType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author heimingz
 */
@Slf4j
@Component
public class DeadlineFirstTranslator {
    private final ItemProcessRepository itemProcessRepo;
    private final OrderRepository orderRepo;
    private final ResourceRepository resourceRepo;
    private final SchedulePeopleListRepository schedulePeopleListRepo;

    @Autowired
    public DeadlineFirstTranslator(ItemProcessRepository itemProcessRepo, OrderRepository orderRepo,
                                   ResourceRepository resourceRepo, SchedulePeopleListRepository schedulePeopleListRepo) {
        this.itemProcessRepo = itemProcessRepo;
        this.orderRepo = orderRepo;
        this.resourceRepo = resourceRepo;
        this.schedulePeopleListRepo = schedulePeopleListRepo;
    }

    /**
     * 构造排程计划表
     *
     * @param scheduleStartTime 排程开始时间
     * @return 排程计划表
     */
    public ScheduleTable getSchedulerTable(LocalDateTime scheduleStartTime) {
        // Step1: 构造资源
        // Step1.1: 班次 TODO: 暂时硬编码排班和班次时间
        Shift workdayEvening = new Shift(Arrays.asList(ShiftType.EVENING, ShiftType.EVENING, ShiftType.EVENING,
                ShiftType.EVENING, ShiftType.EVENING, ShiftType.REST, ShiftType.REST));
        Shift workdayMorning = new Shift(Arrays.asList(ShiftType.MORNING, ShiftType.MORNING, ShiftType.MORNING,
                ShiftType.MORNING, ShiftType.MORNING, ShiftType.REST, ShiftType.REST));
        Shift workdayAllDay = new Shift(Arrays.asList(ShiftType.ALL_DAY, ShiftType.ALL_DAY, ShiftType.ALL_DAY,
                ShiftType.ALL_DAY, ShiftType.ALL_DAY, ShiftType.REST, ShiftType.REST));
        Shift weekAllday = new Shift(Arrays.asList(ShiftType.ALL_DAY, ShiftType.ALL_DAY, ShiftType.ALL_DAY,
                ShiftType.ALL_DAY, ShiftType.ALL_DAY, ShiftType.REST, ShiftType.REST));
        // Step1.2: 班组和设备的排班
        List<com.scheduler.backend.po.SchedulePeopleList> schedulePeopleLists = schedulePeopleListRepo.findAll();
        Map<String, Shift> shiftMap = new HashMap<>(schedulePeopleLists.size() * 2);
        for (com.scheduler.backend.po.SchedulePeopleList schedulePeopleList : schedulePeopleLists) {
            // TODO: 暂时硬编码
            if ("[1, 2, 3, 4, 5]".equals(schedulePeopleList.getWeekdayList())) {
                if ("全天".equals(schedulePeopleList.getScheduleName())) {
                    shiftMap.put(schedulePeopleList.getResourceId(), workdayAllDay);
                } else if ("早班".equals(schedulePeopleList.getScheduleName())) {
                    shiftMap.put(schedulePeopleList.getResourceId(), workdayMorning);
                } else if ("晚班".equals(schedulePeopleList.getScheduleName())) {
                    shiftMap.put(schedulePeopleList.getResourceId(), workdayEvening);
                }
            } else if ("[1, 2, 3, 4, 5, 6, 7]".equals(schedulePeopleList.getWeekdayList())) {
                if ("全天".equals(schedulePeopleList.getScheduleName())) {
                    shiftMap.put(schedulePeopleList.getResourceId(), weekAllday);
                }
            } else {
                log.error(schedulePeopleList.toString());
            }
        }
        // Step1.3: 班组和设备
        List<com.scheduler.backend.po.Resource> rawResources = resourceRepo.findAll();
        Map<String, ResourceGroup> teamMap = new HashMap<>(rawResources.size());
        Map<String, ResourceGroup> equipmentMap = new HashMap<>(rawResources.size());
        for (com.scheduler.backend.po.Resource rawResource : rawResources) {
            ResourceGroup resourceGroup = new ResourceGroup(rawResource.getResourceId(),
                    rawResource.getResourceName(), rawResource.getResourceNum().longValue(), null);

            if ("班组".equals(rawResource.getResourceName())) {
                setEmployeeList(resourceGroup, shiftMap.get(rawResource.getResourceId()));
                teamMap.put(resourceGroup.getResourceId(), resourceGroup);
            } else {
                setEquipmentList(resourceGroup, shiftMap.get(rawResource.getResourceId()));
                equipmentMap.put(resourceGroup.getResourceId(), resourceGroup);
            }
        }

        // Step 2: 产品
        List<com.scheduler.backend.po.ItemProcess> rawItemProcesses = itemProcessRepo.findAll();
        for (int i = 0; i < rawItemProcesses.size(); ++i) {
            rawItemProcesses.get(i).setItemId(Integer.toString(i));
        }
        Map<String, List<com.scheduler.backend.po.ItemProcess>> rawProductMap = rawItemProcesses.stream()
                .collect(Collectors.groupingBy(com.scheduler.backend.po.ItemProcess::getProductId));
        Map<String, Product> productMap = new HashMap<>();
        // Step 2.1: 单个产品
        for (Map.Entry<String, List<com.scheduler.backend.po.ItemProcess>> rawProductEntry : rawProductMap.entrySet()) {
            List<Craft> crafts = new ArrayList<>();
            Map<String, List<com.scheduler.backend.po.ItemProcess>> rawCraftMap = rawProductEntry.getValue().stream()
                    .collect(Collectors.groupingBy(com.scheduler.backend.po.ItemProcess::getArtifactName));

            Map<Integer, Craft> craftMaxItemId = new TreeMap<>();
            // Step 2.1.1: 单个工艺
            for (Map.Entry<String, List<com.scheduler.backend.po.ItemProcess>> rawCraftEntry : rawCraftMap.entrySet()) {
                Map<ResourceGroup, CraftResourceDescription> teamDescripMap = new HashMap<>();
                Map<ResourceGroup, CraftResourceDescription> equipmentDescripMap = new HashMap<>();

                Long maxRequiredEmployeeAmount = Long.MIN_VALUE;
                Double maxTimeCostOfSwitch = Double.MIN_VALUE;
                int maxItemId = Integer.MIN_VALUE;
                for (com.scheduler.backend.po.ItemProcess itemProcess : rawCraftEntry.getValue()) {
                    if (itemProcess.getChangeLineTime() > maxTimeCostOfSwitch) {
                        maxTimeCostOfSwitch = itemProcess.getChangeLineTime();
                    }
                    if (itemProcess.getPeopleNeeded() > maxRequiredEmployeeAmount) {
                        maxRequiredEmployeeAmount = itemProcess.getPeopleNeeded().longValue();
                    }
                    int itemId = Integer.parseInt(itemProcess.getItemId());
                    if (itemId > maxItemId) {
                        maxItemId = itemId;
                    }

                    if (itemProcess.getResourceKind() == null) {
                        continue;
                    }
                    Long capacity = Long.valueOf(itemProcess.getProduction().trim().replace("个/小时", ""));
                    CraftResourceDescription description = new CraftResourceDescription(itemProcess.getResourceKind(), capacity);

                    String resourceName = itemProcess.getResourceName();
                    if (teamMap.containsKey(resourceName)) {
                        teamDescripMap.put(teamMap.get(resourceName), description);
                    } else if (equipmentMap.containsKey(resourceName)) {
                        equipmentDescripMap.put(equipmentMap.get(resourceName), description);
                    }
                }
                Craft craft = new Craft(rawCraftEntry.getKey(), maxRequiredEmployeeAmount, maxTimeCostOfSwitch,
                        equipmentDescripMap.isEmpty() ? null : equipmentDescripMap,
                        teamDescripMap.isEmpty() ? null : teamDescripMap);

                craftMaxItemId.put(maxItemId, craft);
            }
            for (Craft craft: craftMaxItemId.values()) {
                crafts.add(craft);
            }
            // Step 2.1.2: 装配产品
            Product product = new Product(rawProductEntry.getKey(), new ProductBOM(rawProductEntry.getKey(), crafts));
            productMap.put(product.getProductId(), product);
        }

        // Step 3: 获取订单列表
        List<com.scheduler.backend.po.Order> rawOrders = orderRepo.findAll();
        List<Order> orders = new ArrayList<>();
        LocalDateTime lastDeadline = LocalDateTime.MIN;
        for (com.scheduler.backend.po.Order rawOrder : rawOrders) {
            LocalDateTime deadline = rawOrder.getDeadline().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            if (deadline.isAfter(lastDeadline)) {
                lastDeadline = deadline;
            }

            Order order = new Order(rawOrder.getOrderId().toString(), rawOrder.getItemId(),
                    rawOrder.getOrderNum().longValue(), deadline);
            orders.add(order);
        }
        // 排序
        orders.sort(Comparator.comparing(Order::getDeadline).thenComparingLong(Order::getProductAmount));

        // Step 4: 划分订单切片
        List<OrderSlice> orderSlices = new ArrayList<>();
        for (Order order : orders) {
            Product orderProduct = productMap.get(order.getProductId());
            List<Craft> orderCrafts = orderProduct.getProductBOM().getCrafts();
            Long orderMinCapacity = Long.MAX_VALUE;
            // 获取每小时最小产量
            for (Craft craft : orderCrafts) {
                if (craft.getProdTeamDescription() != null) {
                    for (CraftResourceDescription description : craft.getProdTeamDescription().values()) {
                        if (orderMinCapacity > description.getCapacity()) {
                            orderMinCapacity = description.getCapacity();
                        }
                    }
                }
                if (craft.getEquipmentGrpDescription() != null) {
                    for (CraftResourceDescription description : craft.getEquipmentGrpDescription().values()) {
                        if (orderMinCapacity > description.getCapacity()) {
                            orderMinCapacity = description.getCapacity();
                        }
                    }
                }
            }
            // 按最小产量的倍数切分订单
            for (int i = 0; i < orderCrafts.size(); ++i) {
                long minCapacityTimesHours = orderMinCapacity * ScheduleTable.ORDER_SLICE_MINUTES / 60;
                long sliceNum = order.getProductAmount() / minCapacityTimesHours;
                if (order.getProductAmount() % minCapacityTimesHours != 0) {
                    ++sliceNum;
                }
                for (int j = 0; j < sliceNum; ++j) {
                    String sliceId = order.getOrderId() + "_"
                            + j + "/" + (sliceNum - 1) + "_"
                            + i + "/" + (orderCrafts.size() - 1);
                    OrderSlice orderSlice = new OrderSlice(sliceId, order, orderProduct, orderCrafts.get(i), scheduleStartTime);
                    orderSlices.add(orderSlice);
                }
            }
        }
        //设置负载均衡大小 by GFY
        ScheduleTableConstraintProvider.DeviceLoadBalanceData.setMaxCount(orderSlices.size());
        ScheduleTableConstraintProvider.EmployeeLoadBalanceData.setMaxCount(orderSlices.size());

        // 计算时间切片个数
        Duration duration = Duration.between(scheduleStartTime, lastDeadline);
        Long timeslotRange = (duration.toHours() + 7 * 24) / (ScheduleTable.ORDER_SLICE_MINUTES / 60);
        ScheduleTable scheduleTable = new ScheduleTable(timeslotRange, orderSlices);

        return scheduleTable;
    }

    private void setEmployeeList(ResourceGroup team, Shift shift) {
        List<Resource> resources = new ArrayList<>(team.getAmount().intValue());
        for (int i = 0; i < team.getAmount(); i++) {
            resources.add(new Employee(String.valueOf(i), team.getResourceId(), shift));
        }
        team.setResources(resources);
    }

    private void setEquipmentList(ResourceGroup equipments, Shift shift) {
        List<Resource> resources = new ArrayList<>(equipments.getAmount().intValue());
        for (int i = 0; i < equipments.getAmount(); i++) {
            resources.add(new Equipment(String.valueOf(i), equipments.getResourceId(), shift));
        }
        equipments.setResources(resources);
    }
}